package SocialNet;

import org.neo4j.graphalgo.GraphAlgoFactory;
import org.neo4j.graphalgo.PathFinder;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.*;
import org.neo4j.graphdb.traversal.Traverser;
import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.kernel.Traversal;
import org.neo4j.kernel.Uniqueness;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Iterator;  

public class Person {
    static final String USERNAME = "Name";
    static final String GENDER = "Gender";
    private final Node pNode;
    private ArrayList<Status> myStatus;

    public Person (Node pNode) {
        this.pNode = pNode;
        this.myStatus = new ArrayList<Status>();
    }
    
    public Person (Person other) {
    	this.pNode = other.getNode();
    	this.myStatus = new ArrayList<Status>();
    	for(Status s : other.getStatus()){
    		this.myStatus.add(s);
    	}
    }

    public Node getNode() {
        return pNode;
    }

    public String getName() {
        return (String)pNode.getProperty(USERNAME);
    }

    public String getGender() {
        return (String)pNode.getProperty(GENDER);
    }

    @Override
    public boolean equals( Object o ) {
        return o instanceof Person && pNode.equals(((Person)o).getNode());
    }

    @Override
    public String toString() {
        return "Node[" + this.pNode.getId() + "]{Name->\"" + getName() + "\",Gender->\"" + getGender() + "\"}";
    }

    public void addFriend(Person otherPerson) {
        Transaction tx = pNode.getGraphDatabase().beginTx();
        try {
            if (!this.equals(otherPerson)) {
                Relationship friendRel = getFriendRelationshipTo( otherPerson );
                if ( friendRel == null )
                    pNode.createRelationshipTo( otherPerson.getNode(), RelTypes.FRIEND );
                tx.success();
            }
        }
        finally {
            tx.finish();
        }
    }

    public int getNumFriends() {
        return IteratorUtil.count(getFriends());
    }

    public Iterable<Person> getFriends() {
        return getFriendsByDepth(1);
    }

    public void removeFriend( Person otherPerson ) {
        Transaction tx = pNode.getGraphDatabase().beginTx();
        try {
            if ( !this.equals( otherPerson ) )
            {
                Relationship friendRel = getFriendRelationshipTo( otherPerson );
                if ( friendRel != null )
                    friendRel.delete();
                tx.success();
            }
        }
        finally {
            tx.finish();
        }
    }

    /*    public Iterable<Person> getFriendsOfFriends()
    {
        return getFriendsByDepth(2);
    }*/

    public Iterable<Person> getShortestPathTo( Person otherPerson,
            int maxDepth ) {
        // use graph algo to calculate a shortest path
        PathFinder<Path> finder = GraphAlgoFactory.shortestPath(
                Traversal.expanderForTypes( RelTypes.FRIEND, Direction.BOTH ), maxDepth );

        Path path = finder.findSinglePath( pNode,
                otherPerson.getNode() );
        return createPersonsFromNodes( path );
        }

    /*    public Iterable<Person> getFriendRecommendation(
            int numberOfFriendsToReturn )
    {
        HashSet<Person> friends = new HashSet<Person>();
        IteratorUtil.addToCollection( getFriends(), friends );

        HashSet<Person> friendsOfFriends = new HashSet<Person>();
        IteratorUtil.addToCollection( getFriendsOfFriends(), friendsOfFriends );

        friendsOfFriends.removeAll( friends );

        ArrayList<RankedPerson> rankedFriends = new ArrayList<RankedPerson>();
        for ( Person friend : friendsOfFriends )
        {
            int rank = getNumberOfPathsToPerson( friend );
            rankedFriends.add( new RankedPerson( friend, rank ) );
        }

        Collections.sort( rankedFriends, new RankedComparer() );
        trimTo( rankedFriends, numberOfFriendsToReturn );

        return onlyFriend( rankedFriends );
    }*/

    public Iterable<Status> getStatus()
    {
        Relationship firstStatus = pNode.getSingleRelationship(RelTypes.STATUS,
                Direction.OUTGOING);
        if (firstStatus == null) {
            return Collections.emptyList();
        }

        // START SNIPPET: getStatusTraversal
        TraversalDescription traversal = Traversal.description().depthFirst().
                relationships(RelTypes.NEXT).evaluator(Evaluators.excludeStartPosition());
        // END SNIPPET: getStatusTraversal

        return new IterableWrapper<Status, Path> (traversal.traverse(firstStatus.getEndNode())) {
            @Override
            protected Status underlyingObjectToObject( Path path ) {
                return new Status( path.endNode() );
            }
        };
    }
    
    public ArrayList<Status> getStatusList() {
    	return this.myStatus;
    }

    public Iterator<Status> friendStatuses(){
        return new StatusIterator( this );
    }

    public void addStatus(String text)
    {
        Transaction tx = graphDb().beginTx();
        try
        {
            Status oldStatus;
            if (myStatus.size() != 0)
                oldStatus = myStatus.get(myStatus.size()-1);
            else oldStatus = null;
            Node newStatus = createNewStatusNode(text);
            if (myStatus.size() != 0) {
                //pNode.getSingleRelationship( RelTypes.STATUS, Direction.OUTGOING ).delete();
                newStatus.createRelationshipTo(oldStatus.getNode(), RelTypes.NEXT);
            }

            myStatus.add(new Status(newStatus));
            pNode.createRelationshipTo(newStatus, RelTypes.STATUS);
            tx.success();
        }
        finally {
            tx.finish();
        }
    }

    public void likeStatus(Person otherPerson, int idx) {
        Transaction tx = graphDb().beginTx();
        try {
            Relationship friendRel = otherPerson.getFriendRelationshipTo(this);
            if(friendRel != null) {
            	int i = myStatus.get(idx-1).getLike();
            	//System.out.println(i);
            	myStatus.get(idx-1).like();
	            //otherPerson.pNode.createRelationshipTo(myStatus.get(idx).getNode(), RelTypes.LIKE_STATUS);
	            myStatus.get(idx-1).getNode().createRelationshipTo(otherPerson.getNode(), RelTypes.LIKE_STATUS);
	            tx.success();
            }
        }
        finally {
            tx.finish();
        }
    }

    private GraphDatabaseService graphDb() {
        return pNode.getGraphDatabase();
    }

    private Node createNewStatusNode(String text) {
        Node newStatus = graphDb().createNode();
        newStatus.setProperty(Status.TEXT, text);
        newStatus.setProperty(Status.DATE, new Date().getTime());
        newStatus.setProperty(Status.LIKE, 0);
        return newStatus;
    }
    
    private Node updateStatusNode(Node oldNode, int newVote) {
        oldNode.setProperty(Status.LIKE, newVote);
        return oldNode;
    }

    private final class RankedPerson
    {
        final Person person;
        final int rank;

        private RankedPerson( Person person, int rank ) {
            this.person = person;
            this.rank = rank;
        }

        public Person getPerson()
        {
            return person;
        }
        public int getRank()
        {
            return rank;
        }

    }

    private class RankedComparer implements Comparator<RankedPerson>
    {
        public int compare( RankedPerson a, RankedPerson b )
        {
            return b.getRank() - a.getRank();
        }

    }

    private void trimTo( ArrayList<RankedPerson> rankedFriends,
            int numberOfFriendsToReturn )
    {
        while ( rankedFriends.size() > numberOfFriendsToReturn )
        {
            rankedFriends.remove( rankedFriends.size() - 1 );
        }
    }

    private Iterable<Person> onlyFriend( Iterable<RankedPerson> rankedFriends )
    {
        ArrayList<Person> retVal = new ArrayList<Person>();
        for ( RankedPerson person : rankedFriends )
        {
            retVal.add( person.getPerson() );
        }
        return retVal;
    }

    private Relationship getFriendRelationshipTo( Person otherPerson )
    {
        Node otherNode = otherPerson.getNode();
        for ( Relationship rel : pNode.getRelationships( RelTypes.FRIEND ) )
        {
            if ( rel.getOtherNode( pNode ).equals( otherNode ) )
            {
                return rel;
            }
        }
        return null;
    }

    private Iterable<Person> getFriendsByDepth(int depth)
    {
        // return all my friends and their friends using new traversal API
        TraversalDescription travDesc = Traversal.description()
                .breadthFirst()
                .relationships( RelTypes.FRIEND )
                .uniqueness( Uniqueness.NODE_GLOBAL )
                .prune( Traversal.pruneAfterDepth( depth ) )
                .filter( Traversal.returnAllButStartNode() );

        return createPersonsFromPath( travDesc.traverse( pNode ) );
    }

    private IterableWrapper<Person, Path> createPersonsFromPath(
            Traverser iterableToWrap ){
        return new IterableWrapper<Person, Path>( iterableToWrap )
                {
            @Override
            protected Person underlyingObjectToObject( Path path )
            {
                return new Person( path.endNode() );
            }
                };
    }

    private int getNumberOfPathsToPerson( Person otherPerson )
    {
        PathFinder<Path> finder = GraphAlgoFactory.allPaths( Traversal.expanderForTypes( RelTypes.FRIEND, Direction.BOTH ), 2 );
        Iterable<Path> paths = finder.findAllPaths( getNode(), otherPerson.getNode() );
        return IteratorUtil.count( paths );
    }

    private Iterable<Person> createPersonsFromNodes( final Path path )
    {
        return new IterableWrapper<Person, Node>( path.nodes() )
                {
            @Override
            protected Person underlyingObjectToObject( Node node )
            {
                return new Person( node );
            }
                };
    }
    
    public Status getStatusMaxLike() {
    	int count = 0;
    	if (myStatus.size() > 0) {
	    	Status init = myStatus.get(0);
	    	count = init.getLike();
	    	Iterator<Status> i = myStatus.iterator();
	    	//Iterable<Status> getStatus
	    	while(i.hasNext()) {
	    		Status curr = i.next();
	    		if (count < curr.getLike()) {
	    			count = curr.getLike();
	    			init = curr;
	    		}
	    	}
	    	return init;
    	}
    	return null;
    }

    public Status getStatusMinLike() {
    	int count = 0;
    	if (myStatus.size() > 0) {
	    	Status init = myStatus.get(0);
	    	count = init.getLike();
	    	Iterator<Status> i = myStatus.iterator();
	    	//Iterable<Status> getStatus
	    	while(i.hasNext()) {
	    		Status curr = i.next();
	    		if (count > curr.getLike()) {
	    			count = curr.getLike();
	    			init = curr;
	    		}
	    	}
	    	return init;
    	}
    	return null;
    }

}


